﻿

#region Using Statements

using UnityEngine;
using System.Collections.Generic;

#endregion


[RequireComponent(typeof(Animation))]
/// <summary>
/// 
/// </summary>
public class AnimationLibrary : MonoBehaviour
{

    #region Members

    public List<AnimationSet> sets;

    public List<AnimationLayer> layers;

    #endregion


    #region Init

    /// <summary>
    /// 
    /// </summary>
    void Awake()
    {
        int nextId = 0;

        // add all clips to animation controller
        foreach (AnimationLayer layer in layers)
        {
            layer.library = this;

            foreach (AnimationMask mask in layer.masks)
            {
                mask.layer = layer;
                mask.id = nextId++;

                foreach (AnimationSet set in sets)
                {
                    for (int i = 0; i < set.clips.Count; i++)
                    {
                        AnimationVariant var = set.clips[i];

                        if (mask.id == 0)
                        {
                            var.states = new List<AnimationState>();
                            var.name = set.name + "-" + i.ToString();
                            var.set = set;
                        }

                        for (int j = 0; j < var.clips.Count; j++)
                        {
                            string clipName = layer.name + "-" + mask.name + "-" + set.name + "-" + i.ToString() + "-" + j.ToString();
                            animation.AddClip(var.clips[j], clipName);

                            AnimationState anim = animation[clipName];
                            anim.speed = var.playSpeed;
                            anim.layer = mask.tier;
                            anim.blendMode = var.blendMode;

                            var.states.Add(anim);

                            // apply skeleton mask
                            if (mask.chainBoneInclude.Count > 0)
                                foreach (Transform root in mask.chainBoneInclude)
                                {
                                    Transform[] ts = root.GetComponentsInChildren<Transform>();
                                    foreach (Transform t in ts)
                                        anim.AddMixingTransform(t, false);
                                }

                            if (mask.individualBoneInclude.Count > 0)
                                foreach (Transform t in mask.individualBoneInclude)
                                    anim.AddMixingTransform(t, false);

                            if (mask.chainBoneExclude.Count > 0)
                                foreach (Transform root in mask.chainBoneExclude)
                                {
                                    Transform[] ts = root.GetComponentsInChildren<Transform>();
                                    foreach (Transform t in ts)
                                        anim.RemoveMixingTransform(t);
                                }

                            if (mask.individualBoneExclude.Count > 0)
                                foreach (Transform t in mask.individualBoneExclude)
                                    anim.RemoveMixingTransform(t);
                        }
                    }
                }
            }
        }

        // set initial states
        foreach (AnimationLayer layer in layers)
            layer.currentState = FindSet(layer.initialState);
    }


    /// <summary>
    /// 
    /// </summary>
    void Start()
    {

    }

    #endregion


    #region Update

    void Update()
    {
        //if (Input.GetButton("Fire3"))
        //    foreach (AnimationSet set in sets)
        //        foreach (AnimationVariant var in set.clips)
        //            foreach (AnimationState anim in var.states)
        //            {
        //                if (anim.weight > 0.1f)
        //                    Debug.Log(anim.name + "    " + anim.weight + "    " + anim.normalizedTime);
        //            }
    }

    #endregion


    #region Play

    /// <summary>
    /// 
    /// </summary>
    /// <param name="layerName"></param>
    /// <param name="stateName"></param>
    /// <param name="fwd"></param>
    /// <param name="up"></param>
    public void Play(string layerName, string stateName, Vector3 fwd, Vector3 up) { Play(FindLayer(layerName), FindSet(stateName), 0.0f); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="layerName"></param>
    /// <param name="stateName"></param>
    public void Play(string layerName, string stateName) { Play(FindLayer(layerName), FindSet(stateName), 0.0f); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="layer"></param>
    /// <param name="state"></param>
    public void Play(AnimationLayer layer, AnimationSet state) { Play(layer, state, 0.0f); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="layerName"></param>
    /// <param name="stateName"></param>
    /// <param name="fwd"></param>
    /// <param name="up"></param>
    /// <param name="blendFactor"></param>
    public void Play(string layerName, string stateName, float blendFactor) { Play(FindLayer(layerName), FindSet(stateName), blendFactor); }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="set"></param>
    public void Play(AnimationLayer layer, AnimationSet set, float blendFactor)
    {
        if (layer == null || set == null)
            return;

        layer.blendFactor = blendFactor;

        AnimationVariant var = set.pick;

        layer.lastVariant = layer.currentVariant;
        layer.currentVariant = var;

        //if (Input.GetButton("Fire3") && layer.lastVariant != layer.currentVariant)
        //    Debug.Log("Playing: " + layer.currentVariant.name + "  was Playing: " + layer.lastVariant.name);

        foreach (AnimationMask mask in layer.masks)
            Blend(mask, layer.lastVariant, layer.currentVariant);
    }

    #endregion


    #region Find

    /// <summary>
    /// 
    /// </summary>
    /// <param name="traitName"></param>
    /// <returns></returns>
    public AnimationLayer FindLayer(string name)
    {
        for (int i = 0; i < layers.Count; i++)
            if (layers[i].name.CompareTo(name) == 0)
                return layers[i];
        return null;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="traitName"></param>
    /// <returns></returns>
    public AnimationSet FindSet(string name)
    {
        for (int i = 0; i < sets.Count; i++)
            if (sets[i].name.CompareTo(name) == 0)
                return sets[i];
        return null;
    }

    #endregion


    #region Blend

    /// <summary>
    /// 
    /// </summary>
    /// <param name="fadeTime"></param>
    private void Blend(AnimationMask mask, AnimationVariant from, AnimationVariant to)
    {
        if (to == null || mask == null || to.clips.Count < 1)
            return;

        float t = 0.0f;

        if (from != null)
            for (int i = 0; i < from.clips.Count; i++)
            {
                AnimationState anim = animation[from.GetName(mask, i)];
                if (from.syncClips)
                {
                    if (i == 0)
                        t = anim.normalizedTime;
                    else
                        anim.normalizedTime = t;
                }

                animation.Blend(anim.name, 0.0f, from.fadeOutTime);
            }


        float f = Mathf.Clamp(mask.layer.blendFactor, 0.0f, 1.0f) * (to.clips.Count - 1);
        

        for (int i = 0; i < to.clips.Count; i++)
        {
            string name = to.GetName(mask, i);
            AnimationState anim = animation[name];

            if (mask.layer.lastVariant != to)
                anim.normalizedTime = 0.0f;

            if (to.syncClips)
            {
                if (i == 0)
                    t = anim.normalizedTime;
                else
                    anim.normalizedTime = t;
            }

            float w = Mathf.Clamp(1.0f - Mathf.Abs(i - f), 0.0f, 1.0f);
            animation.Blend(name, w * to.blendWeight * mask.blendWeight, to.fadeInTime);
        }
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="layer"></param>
    public void StopLayer(AnimationLayer layer)
    {
        if (layer == null)
            return;

        layer.lastVariant = layer.currentVariant;
        layer.currentVariant = null;

        foreach (AnimationMask mask in layer.masks)
            foreach (AnimationSet set in sets)
                foreach (AnimationVariant v in set.clips)
                    for (int i = 0; i < v.clips.Count; i++)
                        animation.Blend(v.GetName(mask, i), 0.0f, v.fadeOutTime);
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="layerName"></param>
    public void StopLayer(string layerName) { StopLayer(FindLayer(layerName)); }

    #endregion


    #region ReRoll

    /// <summary>
    /// 
    /// </summary>
    void OnAnimationReRoll(AnimationEvent evt)
    {
        foreach (AnimationLayer layer in layers)
            foreach (AnimationMask mask in layer.masks)
                if (mask.tier == evt.animationState.layer)
                {
                    if (layer.currentState != null)
                        Blend(mask, layer.currentVariant, layer.currentState.pick);
                    return;
                }
    }

    #endregion

}


[System.Serializable]
/// <summary>
/// 
/// </summary>
public class AnimationMask
{
    public string name;
    public float blendWeight = 1.0f;
    public int tier = 0;

    public List<Transform> chainBoneInclude;
    public List<Transform> individualBoneInclude;
    public List<Transform> chainBoneExclude;
    public List<Transform> individualBoneExclude;

    public AnimationLayer layer { get; set; }
    public int id { get; set; }
}


[System.Serializable]
/// <summary>
/// 
/// </summary>
public class AnimationLayer
{
    public string name;
    public string initialState = "idle";
    public List<AnimationMask> masks;

    public float blendFactor { get; set; }
    public AnimationSet currentState { get { return currentVariant == null ? null : currentVariant.set; } set { library.Play(this, value); } }
    public AnimationVariant lastVariant { get; set; }
    public AnimationVariant currentVariant { get; set; }
    public AnimationLibrary library { get; set; }
}



[System.Serializable]
/// <summary>
/// 
/// </summary>
public class AnimationSet
{
    public string name;
    public List<AnimationVariant> clips;
    public AnimationVariant pick { get { return clips.RandomItem(); } }
}


[System.Serializable]
/// <summary>
/// 
/// </summary>
public class AnimationVariant
{
    public List<AnimationClip> clips;    
    public float playSpeed = 1.0f;
    public float blendWeight = 1.0f;
    public float fadeInTime = 0.1f;
    public float fadeOutTime = 0.1f;
    public bool syncClips = true;
    public AnimationBlendMode blendMode = AnimationBlendMode.Blend;

    public string name { get; set; }
    public AnimationSet set { get; set; }
    public List<AnimationState> states { get; set; }
    public AnimationState GetClip(AnimationMask layer) { return states[layer.id]; }
    public string GetName(AnimationMask mask, int clipNum) { return mask.layer.name + "-" + mask.name + "-" + name + "-" + clipNum.ToString(); }
    public string GetName(AnimationMask mask) { return GetName(mask, 0); }
}